<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace core\http\bags;

/**
 * HeaderBag is a container for HTTP headers.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HeaderBag {
	protected $headers;
	protected $cookies;
	protected $cacheControl;
	
	/**
	 * Constructor.
	 *
	 * @param array $headers An array of HTTP headers
	 */
	public function __construct(array $headers = array()) {
		$this->cacheControl = array();
		$this->cookies = array();
		$this->headers = array();
		foreach( $headers as $key => $values ) {
			$this->set( $key, $values );
		}
	}
	
	/**
	 * Returns the headers.
	 *
	 * @return array An array of headers
	 */
	public function all() {
		return $this->headers;
	}
	
	/**
	 * Returns the parameter keys.
	 *
	 * @return array An array of parameter keys
	 */
	public function keys() {
		return array_keys( $this->headers );
	}
	
	/**
	 * Replaces the current HTTP headers by a new set.
	 *
	 * @param array  $headers An array of HTTP headers
	 */
	public function replace(array $headers = array()) {
		$this->headers = array();
		$this->add( $headers );
	}
	
	/**
	 * Adds new headers the current HTTP headers set.
	 *
	 * @param array  $headers An array of HTTP headers
	 */
	public function add(array $headers) {
		foreach( $headers as $key => $values ) {
			$this->set( $key, $values );
		}
	}
	
	/**
	 * Returns a header value by name.
	 *
	 * @param string  $key     The header name
	 * @param mixed   $default The default value
	 * @param Boolean $first   Whether to return the first value or all header values
	 *
	 * @return string|array The first header value if $first is true, an array of values otherwise
	 */
	public function get($key, $default = null, $first = true) {
		$key = strtr( strtolower( $key ), '_', '-' );
		
		if( !array_key_exists( $key, $this->headers ) ) {
			if( null === $default ) {
				return $first ? null : array();
			}
			
			return $first ? $default : array( $default );
		}
		
		if( $first ) {
			return count( $this->headers[$key] ) ? $this->headers[$key][0] : $default;
		}
		
		return $this->headers[$key];
	}
	
	/**
	 * Sets a header by name.
	 *
	 * @param string       $key     The key
	 * @param string|array $values  The value or an array of values
	 * @param Boolean      $replace Whether to replace the actual value of not (true by default)
	 */
	public function set($key, $values, $replace = true) {
		$key = strtr( strtolower( $key ), '_', '-' );
		
		$values = ( array ) $values;
		
		if( true === $replace || !isset( $this->headers[$key] ) ) {
			$this->headers[$key] = $values;
		} else {
			$this->headers[$key] = array_merge( $this->headers[$key], $values );
		}
		
		if( 'cache-control' === $key ) {
			$this->cacheControl = $this->parseCacheControl( $values[0] );
		}
	}
	
	/**
	 * Returns true if the HTTP header is defined.
	 *
	 * @param string $key The HTTP header
	 *
	 * @return Boolean true if the parameter exists, false otherwise
	 */
	public function has($key) {
		return array_key_exists( strtr( strtolower( $key ), '_', '-' ), $this->headers );
	}
	
	/**
	 * Returns true if the given HTTP header contains the given value.
	 *
	 * @param string $key   The HTTP header name
	 * @param string $value The HTTP value
	 *
	 * @return Boolean true if the value is contained in the header, false otherwise
	 */
	public function contains($key, $value) {
		return in_array( $value, $this->get( $key, null, false ) );
	}
	
	/**
	 * Removes a header.
	 *
	 * @param string $key The HTTP header name
	 */
	public function remove($key) {
		$key = strtr( strtolower( $key ), '_', '-' );
		
		unset( $this->headers[$key] );
		
		if( 'cache-control' === $key ) {
			$this->cacheControl = array();
		}
	}
	
	/**
	 * Sets a cookie.
	 *
	 * @param Cookie $cookie
	 * @return void
	 */
	public function setCookie(Cookie $cookie) {
		$this->cookies[$cookie->getName()] = $cookie;
	}
	
	/**
	 * Removes a cookie from the array, but does not unset it in the browser
	 *
	 * @param string $name
	 * @return void
	 */
	public function removeCookie($name) {
		unset( $this->cookies[$name] );
	}
	
	/**
	 * Whether the array contains any cookie with this name
	 *
	 * @param string $name
	 * @return Boolean
	 */
	public function hasCookie($name) {
		return isset( $this->cookies[$name] );
	}
	
	/**
	 * Returns a cookie
	 *
	 * @param string $name
	 *
	 * @throws \InvalidArgumentException When the cookie does not exist
	 *
	 * @return Cookie
	 */
	public function getCookie($name) {
		if( !$this->hasCookie( $name ) ) {
			throw new \InvalidArgumentException( sprintf( 'There is no cookie with name "%s".', $name ) );
		}
		
		return $this->cookies[$name];
	}
	
	/**
	 * Returns an array with all cookies
	 *
	 * @return array
	 */
	public function getCookies() {
		return $this->cookies;
	}
	
	/**
	 * Returns the HTTP header value converted to a date.
	 *
	 * @param string    $key     The parameter key
	 * @param \DateTime $default The default value
	 *
	 * @return \DateTime The filtered value
	 */
	public function getDate($key,\DateTime $default = null) {
		if( null === $value = $this->get( $key ) ) {
			return $default;
		}
		
		if( false === $date = \DateTime::createFromFormat( DATE_RFC2822, $value ) ) {
			throw new \RuntimeException( sprintf( 'The %s HTTP header is not parseable (%s).', $key, $value ) );
		}
		
		return $date;
	}
	
	public function addCacheControlDirective($key, $value = true) {
		$this->cacheControl[$key] = $value;
		
		$this->set( 'Cache-Control', $this->getCacheControlHeader() );
	}
	
	public function hasCacheControlDirective($key) {
		return array_key_exists( $key, $this->cacheControl );
	}
	
	public function getCacheControlDirective($key) {
		return array_key_exists( $key, $this->cacheControl ) ? $this->cacheControl[$key] : null;
	}
	
	public function removeCacheControlDirective($key) {
		unset( $this->cacheControl[$key] );
		
		$this->set( 'Cache-Control', $this->getCacheControlHeader() );
	}
	
	protected function getCacheControlHeader() {
		$parts = array();
		ksort( $this->cacheControl );
		foreach( $this->cacheControl as $key => $value ) {
			if( true === $value ) {
				$parts[] = $key;
			} else {
				if( preg_match( '#[^a-zA-Z0-9._-]#', $value ) ) {
					$value = '"' . $value . '"';
				}
				
				$parts[] = "$key=$value";
			}
		}
		
		return implode( ', ', $parts );
	}
	
	/**
	 * Parses a Cache-Control HTTP header.
	 *
	 * @param string $header The value of the Cache-Control HTTP header
	 *
	 * @return array An array representing the attribute values
	 */
	protected function parseCacheControl($header) {
		$cacheControl = array();
		preg_match_all( '#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER );
		foreach( $matches as $match ) {
			$cacheControl[strtolower( $match[1] )] = isset( $match[2] ) && $match[2] ? $match[2] : (isset( $match[3] ) ? $match[3] : true);
		}
		
		return $cacheControl;
	}
}
